<?php
/*
   GMLP - The GMLP Markup Language Processor; version 1.1
   This code translates* any markup language to any other.**
   Copyright 2013-2014, Greg Jennings.
   License: The (MIT|PHP|Apache|Mozilla) License.
   See GMLP.TXT for the test text and the documentation.

*  I use the word "translate" but actually what happens is conversion 
   (or perhaps even more accurately substitution), and I interchange 
   the two words at times depending on context.
** A bit naive perhaps. Only HTML output is easy (i.e. Textile to 
   Markdown will not be).
*/


define('GMLP_FUNC','gmlp_func.php');	// default functions


/* gmlp_convert - convert string according to $gmlp_translate data */

// function _gmlp(), below, stores definition data

function gmlp_convert($data) {

	if (_gmlp('STRIP'))
		$data = str_replace(_gmlp('STRIP'),'',$data);
	$data = explode(_gmlp('NEWLINE'),$data);

	gmlp_hooks('convert',$data);

	$p = EOL_P1;				// the EOL state flag
	$eol = _gmlp('EOL');			// the EOL values (array)
	$opt = _gmlp('OPT');			// options array
	$body = '';                     	// return value

	while (($_ = array_shift($data)) !== NULL) {
		if ($_ == '')
			continue;

# see end of file for gotoless version
		if (gmlp_skip($_))
			goto skip;

		if (gmlp_blocks($_,$data))
			goto skip2;

		if (isset($opt['/G']) && isset($data[0]) && $data[0]) {
			gmlp_line($_);
			while (isset($data[0]) && $data[0]) {
				$t = array_shift($data);
				gmlp_line($t);
				$_ .= ' '.$t;
			}
		}

		gmlp_convert_str($_);
skip:
		$_ = $eol[$p].$_;		// prepends <p> or <br>
		$p = EOL_BR;			// set to <br>

		// look to next line
		if (isset($data[0]) && $data[0] == '') {
			$_ .= $eol[EOL_P2];	// adds </p>
skip2:			$p = EOL_P1;		// reset to <p>
		}

		$body .= $_.$eol[EOL_NL];
	}

	if ($p == 0)
		$body .= $eol[EOL_P2];

	return $body;
}
/* NOTES
Storing the output as an array of strings (and imploding for the return) 
would allow for more processing capability. This would allow for setext-style 
headers to be parsed but would require some sort of meta data/API to do so.

The function gmlp_convert_str() allows for span markup within blocks; for 
blocks within blocks this function could recurse -- but has not been tested 
(another value for 'convert' can be created to do this).
*/

/* gmlp_convert_str - do all the line conversions at once */

function gmlp_convert_str(&$_) {

	gmlp_inlines($_);
	gmlp_entities($_);
	gmlp_chars($_);
}

/* gmlp_blocks - see if line to block; start of block (process block) */

function gmlp_blocks(&$_, &$data) {

	if (gmlp_lines($_))
		return TRUE;

	if (gmlp_convert_block($_,$data))
		return TRUE;

	return FALSE;
}


/* gmlp_skip - skip over lines that match a RE */

function gmlp_skip(&$_) {

	foreach (_gmlp('skip') as $s)
		if (preg_match($s,$_,$res)) {
			if (isset($res[1]))
				$_ = $res[1];
			return TRUE;
		}

	return FALSE;
}

/* gmlp_lines - REs to convert lines - skip other conversions */

function gmlp_lines(&$_) {

	gmlp_hooks('lines',$_);

	foreach (_gmlp('lines') as $re => $repl) {
		if (preg_match($re,$_,$res)) {
			if ($repl == '') {
				if (isset($res[1]))
					$_ = $res[1];
				return TRUE;
			}
			if (function_exists($repl)) {
				// first subpattern is replacement
				if (isset($res[1]))
					$_ = $res[1];
# might update the specification to: $repl($_,$res);
				$_ = $repl($_);
				return TRUE;
			}

			// first subpattern is converted
			if (isset($res[1]))
				gmlp_convert_str($_);

			// if no subpatterns just substitute
			if (!isset($res[1]))
				$_ = $repl;
			else
				$_ = preg_replace($re,$repl,$_);
			return TRUE;
		}
	}

	return FALSE;
}

/* gmlp_inlines - apply REs/functions to lines */

function gmlp_inlines(&$_) {

	foreach (_gmlp('inlines') as $re => $repl) {
		if (function_exists($repl)) {
			if (preg_match_all($re,$_,$res))
				foreach ($res[0] as $k => $v)
					$_ = str_replace($v,$repl($res[1][$k]),$_);
		}
		else
		if (preg_match($re,$_))
			$_ = preg_replace($re,$repl,$_);
	}
}

/* gmlp_entities - string replacements on each line */

function gmlp_entities(&$_) {

	if (!_gmlp('entitytranslate'))
		return;

	foreach (_gmlp('entities') as $str => $replace)
		$_ = str_replace($str,$replace,$_);
}

/* gmlp_chars - basic bold, italic, etc. */

function gmlp_chars(&$_) {

	$beg = _gmlp('wordbeg');
	$end = _gmlp('wordend');

	foreach (_gmlp('chars') as $char => $tag) {
		$char = addcslashes($char,'*^+[]/\\?.|{}');
		$repl = $beg.$char.'(.*)'.$char.$end;
		$tags = '$1<'.$tag.'>$2</'.$tag.'>$3';
		$_ = preg_replace($repl,$tags,$_);
	}
	return $_;
}


/* gmlp_line - REs applied to each line of block conversions */

function gmlp_line(&$_) {

	foreach (_gmlp('line') as $re => $repl)
		if (preg_match($re,$_))
			$_ = preg_replace($re,$repl,$_);
}

/* gmlp_convert_block - convert lines between start and end REs */

// called with the input array starting one line past the start tag, 
// and the start tag line (both as references; the array will be 
// modified if block tag found, which is why array_shift is used 
// rather than foreach)

function gmlp_convert_block(&$s, &$data) {

	if (!($block = _gmlp_lookup_block($s)))
		return FALSE;

	$end = $block['end'];
	$pre = $block['pre'];
	$post = $block['post'];
	$first = $block['first'];
	$last = $block['last'];
	$repl = $block['replace'];
	$convert = $block['convert'];
	$nl = isset($block['newline']);

	if (isset($block['function'])) {
		$func = $block['function'];
		$func($data,$s, $block);
		return TRUE;
	}

	if ($first) {
		if ($convert == 0)
			gmlp_line($s);
		else
			gmlp_convert_str($_);

		if (function_exists($repl))
			$s = $repl($s);

		$s = $pre.$s."\n";
	}
	else
		$s = $pre;

	$p = 0;
	while (($_ = array_shift($data)) !== NULL) {
		if (preg_match($end,$_))
			break;

		if ($convert == 0)
			gmlp_line($_);
		else
			gmlp_convert_str($_);

		if ($_ == '' && !$nl)
			continue;

		if (function_exists($repl))
			$_ = $repl($_);
		else
		if ($repl)
			$_ = $_.$repl;

		$s .= $_."\n";
	}

	if (isset($block['continue']))
		array_unshift($data,$_);

	if ($last === 0)			// eat last newline
		$s = substr_replace($s,'',-1);

	if ($last) {
		if (!$convert)
			gmlp_line($_);
		else
			gmlp_convert_str($_);
		$s .= $_;
	}

	if (function_exists($post))	// 'post' is either a function
		$s = $post($s);		//  or string to append
	else				//   might want to do more
		$s .= $post;

	return TRUE;
}

/* gmlp_read_block - to be used by definition file to read a block */

function gmlp_read_block(&$data, $s, $re, $string = FALSE) {

	if ($re['first'])
		if ($string)
			$block = "$s\n";
		else
			$block[] = $s;

	$end = $re['end'];
	while (($_ = array_shift($data)) !== NULL) {
		if (preg_match($end,$_))
			break;
		if ($string)
			$block .= "$_\n";
		else
			$block[] = $_;
	}

	if ($re['continue'])
		array_unshift($data,$_);

	return $block;
}

/* gmlp_open - read a translation data file */

function gmlp_open($definitions, $functions = NULL) {

	if ($functions === '')
		$functions = dirname($definitions).GMLP_FUNC;
	if ($functions)
		include $functions;
	include $definitions;
	return _gmlp($gmlp_translate);
}

/* gmlp_add - add data to a regular array */

function gmlp_add($section, $data) {

	_gmlp($section,$data);
}

/* gmlp_add_block - add a block array */

function gmlp_add_block($name, $data) {

	_gmlp($name,$data,TRUE);
}

/* gmlp_hooks - function callback */

function gmlp_hooks($name, &$data) {

	if (($f = _gmlp('HOOKS')) && isset($f[$name])) {
		if (function_exists($f = $f[$name]))
			$f($data);
	}
}

/* internal functions */

// It just became a habit of mine to prepend an underscore on all functions 
// that are only used in one file, i.e. this function is just to support the 
// functions here and is not expected to be called by any other code.

/* _gmlp - initialize (on first call) then return data array */

// should be initialized with an array, see gmlp_open(); see also gmlp_add()
// if $var (data array member) is not defined return array() so that the 
// foreach blocks above "do nothing"

function _gmlp($var = '', $data = NULL, $block = NULL) {
static $gmlp_translate = array();

	if ($gmlp_translate == array()) {
		if ($data) {
			echo("data passed to GMLP before init");
			return FALSE;
		}

		if (!is_array($var)) {
			echo("invalid '$var' passed to GMLP");
			return FALSE;
		}

		$gmlp_translate = $var;
		return TRUE;
	}

	// adding to a non block array?
	if ($data && !$block) {
		foreach ($data as $key => $value)
			$gmlp_translate[$var][$key] = $value;
		return;
	}
	// adding a block array?
	if ($data && $block) {
		$gmlp_translate['blocks'][$var] = $data;
		return;
	}

	// return member 
	return (isset($gmlp_translate[$var])) ? $gmlp_translate[$var] : array();
}

/* index into data's 'blocks' section and return one if match */

function _gmlp_lookup_block($_) {

	foreach (_gmlp('blocks') as $b)
		if (preg_match($b['begin'],$_))
			return _gmlp_def_block($b);
	return NULL;
}

/* default a block definition */

function _gmlp_def_block(&$block) {

	if (!isset($block['end'])) $block['end'] = '' ;
	if (!isset($block['pre'])) $block['pre'] = '';
	if (!isset($block['post'])) $block['post'] = '';
	if (!isset($block['first'])) $block['first'] = '';
	if (!isset($block['last'])) $block['last'] = '';
	if (!isset($block['replace'])) $block['replace'] = '';
	if (!isset($block['convert'])) $block['convert'] = '';
	return $block;
}

/* gotoless version

function gmlp_convert($data) {

	$data = str_replace("\r",'',$data);
	$data = explode("\n",$data);

	$p = EOL_P1;				// the EOL state flag
	$eol = _gmlp('eol');			// the EOL values (array)

	$body = '';                     	// return value

	while (($_ = array_shift($data)) !== NULL) {
		if ($_ == '')
			continue;

		if (!gmlp_skip($_)) {

			if (gmlp_blocks($_,$data)) {
				$p = EOL_P1;
				$body .= $_.$eol[EOL_NL];
				continue;
			}

			gmlp_inlines($_);
			gmlp_entities($_);
			gmlp_chars($_);
		}

		$_ = $eol[$p].$_;		// prepends <p> or <br>
		$p = EOL_BR;			// set to <br>

		// look to next line
		if (isset($data[0]) && $data[0] == '') {
			$_ .= $eol[EOL_P2];	// adds </p>
			$p = EOL_P1;		// reset to <p>
		}

		$body .= $_.$eol[EOL_NL];
	}

	if ($p == 0)
		$body .= $eol[EOL_P2];

	return $body;
}

*/
